package service

import (
	"context"
	"database/sql"
	"errors"

	dbmodel "gitee.com/hp-huiw/family-expense-api/internal/db/model"
	"gitee.com/hp-huiw/family-expense-api/internal/dto"
	"gitee.com/hp-huiw/family-expense-api/internal/repository"
	"gitee.com/hp-huiw/my-go-core/date"
	"gitee.com/hp-huiw/my-go-core/util"
	"github.com/samber/lo"
)

type JourneyService interface {
	Query(ctx context.Context, accountGroupID string, pageSize int64, pageIndex int64) ([]dto.JourneyResponse, error)
	Add(ctx context.Context, accountGroupID string, data *dto.JourneyRequest) error
	Update(ctx context.Context, accountGroupID string, data *dto.JourneyRequest) error
	Delete(ctx context.Context, accountGroupID string, id string) (bool, error)
	DeleteWithTx(ctx context.Context, tx *sql.Tx, accountGroupID string) error
}

func NewJourneyService(db *sql.DB) JourneyService {
	return &journeyService{
		db:                db,
		journeyRepository: repository.NewJourneyRepository(db),
	}
}

// Queries all journeys which associated with provided account group.
func (s *journeyService) Query(ctx context.Context, accountGroupID string, pageSize int64, pageIndex int64) ([]dto.JourneyResponse, error) {
	query, err := s.journeyRepository.Query(ctx, accountGroupID, pageSize, pageIndex)

	if err != nil {
		return nil, err
	}

	regionService := NewRegionService(s.db)

	result := lo.Map(query, func(item repository.Journey, _ int) dto.JourneyResponse {
		return dto.JourneyResponse{
			ID:            item.ID,
			StartDate:     item.StartDate,
			EndDate:       item.EndDate,
			RegionID:      item.RegionID,
			RegionTitle:   item.RegionTitle,
			RegionFlagUrl: regionService.GetFlagUrl(item.RegionName),
		}
	})

	return result, nil
}

// Adds new journey for account group.
func (s *journeyService) Add(ctx context.Context, accountGroupID string, data *dto.JourneyRequest) error {
	model, err := s.mapToDBModel(accountGroupID, data)

	if err != nil {
		return err
	}

	return s.journeyRepository.Insert(ctx, model)
}

// Updates journey info based on provided map data and its id.
func (s *journeyService) Update(ctx context.Context, accountGroupID string, data *dto.JourneyRequest) error {
	model, err := s.mapToDBModel(accountGroupID, data)

	if err != nil {
		return err
	}

	return s.journeyRepository.Update(ctx, model)
}

// Delete deletes the Journey with provided account group id and id.
func (s *journeyService) Delete(ctx context.Context, accountGroupID string, id string) (bool, error) {
	isDeleted := false

	// Checks if this journey contains any income/expense.
	exists, err := NewJourneyIncomeExpenseDetailService(s.db).Exists(ctx, accountGroupID, id)

	if err != nil {
		return false, err
	}

	if !exists {
		// If this journey contains no income/expense then deletes it.
		err = s.journeyRepository.Delete(ctx, accountGroupID, id)

		if err != nil {
			return false, err
		}

		isDeleted = true
	}

	return isDeleted, nil
}

func (s *journeyService) DeleteWithTx(ctx context.Context, tx *sql.Tx, accountGroupID string) error {
	return s.journeyRepository.DeleteWithTx(ctx, tx, accountGroupID)
}

func (r *journeyService) mapToDBModel(accountGroupID string, data *dto.JourneyRequest) (*dbmodel.Journey, error) {
	startDate, err := date.ParseFromLongDateTime(data.StartDate)

	if err != nil {
		return nil, errors.New("invalid start date")
	}

	endDate, err := date.ParseFromLongDateTime(data.EndDate)

	if err != nil {
		return nil, errors.New("invalid end date")
	}

	model := dbmodel.Journey{
		ID:             util.GenerateUUIDWithoutDashes(),
		AccountGroupID: accountGroupID,
		StartDate:      startDate,
		EndDate:        endDate,
		RegionID:       data.Region.ID,
	}

	return &model, nil
}

// Container of JourneyService functions.
type journeyService struct {
	db                *sql.DB
	journeyRepository repository.JourneyRepository
}
